import os
from enum import Enum
from inspect import currentframe
from logging import Formatter, Handler, Logger, StreamHandler, getLogger, handlers, DEBUG
from types import FrameType
from typing import Dict, Tuple, overload

import colorlog


class LogRenderTemplate(Enum):
  TITLE = 0,
  LINE = 1,


class LogRenderItem(Enum):
  FILENAME = 0,
  FILEPATH = 1,
  FUNCTION = 2,
  LINE = 3,


class LogUtil:
  """日志工具类"""
  logger: Logger = None
  format: str = '%(asctime)s %(levelname)s: %(message)s'
  colors: Dict = {
      'DEBUG': 'cyan',
      'INFO': 'blue',
      'WARNING': 'yellow',
      'ERROR': 'red',
      'CRITICAL': 'red,bg_white',
  }
  # 颜色格式
  fmt_colored: Formatter = None
  fmt_colorless: Formatter = None
  # 日志输出端
  console_handler: Handler = None
  file_handler: Handler = None

  # 堆帧
  lastframe: FrameType = None

  @classmethod
  def init(cls, logname, console=False):
    """
    使用前需要初始化，输入生成的日志文件名\n
    注意：默认按天生成日志，且保留最近一周的日志文件
    """
    if not cls.logger:
      cls.logger = getLogger(logname)
      cls.logger.setLevel(DEBUG)
      # 有颜色格式
      cls.fmt_colored = colorlog.ColoredFormatter(
          f'%(log_color)s{cls.format}',
          datefmt=None,
          reset=True,
          log_colors=cls.colors
      )
      # 无颜色格式
      cls.fmt_colorless = Formatter(cls.format)
      # 输出到控制台和文件
      if console:
        cls.console_handler = StreamHandler()
      cls.file_handler = handlers.TimedRotatingFileHandler(
          filename=logname,
          when='D',
          backupCount=3,
          encoding='utf-8'
      )

  @classmethod
  def open(cls):
    # 日志打印到控制台
    if cls.console_handler:
      cls.console_handler.setFormatter(cls.fmt_colored)
      cls.logger.addHandler(cls.console_handler)
    # 日志打印到文件
    assert cls.file_handler is not None, "请先添加文件处理者 (file_handler)"
    if cls.file_handler:
      cls.file_handler.setFormatter(cls.fmt_colorless)
      cls.logger.addHandler(cls.file_handler)

  @classmethod
  def close(cls):
    if cls.console_handler:
      cls.logger.removeHandler(cls.console_handler)
    cls.logger.removeHandler(cls.file_handler)

  @classmethod
  def draft(
      cls,
      writer,
      *msg: Tuple,
      title: str = None,
      isWriteLineInfo: bool = True
  ):
    """草稿写入

    Args:
        writer (Handler): 写入器
        title (str, optional): _description_. Defaults to None.
        isWriteTitle (bool, optional): _description_. Defaults to True.
        isWriteLineInfo (bool, optional): _description_. Defaults to True.
    """

    if title:
      writer(cls.render(LogRenderTemplate.TITLE, title))
    if isWriteLineInfo:
      writer(cls.render(LogRenderTemplate.LINE))
    for message in msg:
      writer(message)

  @classmethod
  def render(cls, template: LogRenderTemplate, *args):
    templateStr = ''
    if template is LogRenderTemplate.TITLE:
      title, = args
      templateStr = "< {} >".format(title).center(100, "-")
    elif template == LogRenderTemplate.LINE:
      # 使用预定义模板渲染
      if len(args) > 0:
        templateStr, = args
        for name, item in LogRenderItem.__members__.items:
          key = f'%{name}'
          if templateStr.index(key) != -1:
            templateStr.replace(key, cls.renderItem(name))
      else:
        # 默认模板, 当没有传来预定义模板使用默认模板
        templateStr = f'< [{cls.renderItem(LogRenderItem.FILENAME)}] - [{cls.renderItem(LogRenderItem.FUNCTION)}] - [{cls.renderItem(LogRenderItem.LINE)}] >'
      #endif
    return templateStr

  @classmethod
  def renderItem(cls, key):
    lastframe = cls.lastframe
    filepath = lastframe.f_code.co_filename

    if key == LogRenderItem.FILENAME:
      filename = os.path.basename(filepath)
      return filename
    #endif
    elif key == LogRenderItem.FILEPATH:
      return filepath
    #endif
    elif key == LogRenderItem.FUNCTION:
      funcn = lastframe.f_code.co_name
      return funcn
      pass
    #endif
    elif key == LogRenderItem.LINE:
      lineo = lastframe.f_lineno
      return lineo
    #endif
    pass

  @classmethod
  def debug(cls, *msg, title=None, isWriteLineInfo=False):
    cls.open()
    cls.lastframe = currentframe().f_back
    cls.draft(
        cls.logger.debug,
        *msg,
        title=title,
        isWriteLineInfo=isWriteLineInfo
    )
    cls.close()

  # @testFrame
  @classmethod
  def info(cls, *msg, title=None, isWriteLineInfo=False):
    cls.open()
    cls.lastframe = currentframe().f_back
    cls.draft(
        cls.logger.info,
        *msg,
        title=title,
        isWriteLineInfo=isWriteLineInfo
    )
    cls.close()

  @classmethod
  def warn(cls, *msg, title=None, isWriteLineInfo=True):
    cls.open()
    cls.lastframe = currentframe().f_back
    cls.draft(
        cls.logger.warn,
        *msg,
        title=title,
        isWriteLineInfo=isWriteLineInfo
    )
    cls.close()

  @overload
  def error(cls, *msg: Tuple[str], title: str = None, isWriteLineInfo=True):
    pass

  @overload
  def error(cls, ex: Exception, *msg: Tuple[str], isWriteLineInfo=True):
    pass

  @classmethod
  def error(
      cls,
      ex: Exception,
      *msg,
      title: str = None,
      isWriteLineInfo=True
  ):
    cls.open()
    cls.lastframe = currentframe().f_back

    if isinstance(ex, Exception):
      class_name = ex.__class__.__name__
      msg = (
          f"错误信息：{str(ex)}",
          *msg,
      )
      if title:
        title += ' ' + class_name
      else:
        title = class_name
    cls.draft(
        cls.logger.error,
        *msg,
        title=title,
        isWriteLineInfo=isWriteLineInfo
    )
    cls.close()

  @classmethod
  def critical(cls, *msg, title=None, isWriteLineInfo=True):
    cls.open()
    cls.lastframe = currentframe().f_back
    cls.draft(
        cls.logger.critical,
        *msg,
        title=title,
        isWriteLineInfo=isWriteLineInfo
    )
    cls.close()
